Split trivial-httpd into separate binary
authorColin Walters <walters@verbum.org>
Fri, 30 Dec 2016 19:18:34 +0000 (14:18 -0500)
committerAtomic Bot <atomic-devel@projectatomic.io>
Wed, 4 Jan 2017 16:32:11 +0000 (16:32 +0000)
Working on the libcurl backend, I hit the issue that the trivial-httpd program
depends on libsoup. I briefly considered having two versions, but libcurl is
client only, and moreover trivial-httpd is no longer trivial - it has various
features which are used by the test suite extensively.

Hence, what we'll do is build it as a separate binary which links to libsoup,
and use it during the tests. We *also* currently still provide `ostree
trivial-httpd` since some things use it like `rpm-ostree-toolbox` and the
Cockpit tests.

After those are ported to use some other webserver, I plan to add a build-time
option to drop it.

Closes: #636
Approved by: jlebon

Makefile-ostree.am
Makefile-tests.am
src/ostree/ostree-trivial-httpd.c [new file with mode: 0644]
src/ostree/ot-builtin-trivial-httpd.c
tests/libtest.sh
tests/test-commit-sign.sh
tests/test-pull-contenturl.sh
tests/test-pull-metalink.sh
tests/test-pull-mirrorlist.sh
tests/test-pull-override-url.sh

index d46fc2252843792e4851d46278920c0fdad0be8f..05fec155207fd65007205d1c2ff8d059d708aec6 100644 (file)
@@ -103,7 +103,8 @@ EXTRA_DIST += src/ostree/parse-datetime.y
 CLEANFILES += src/ostree/parse-datetime.c
 
 ostree_bin_shared_cflags = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree \
-       -I$(srcdir)/src/ostree -I$(srcdir)/libglnx $(OT_INTERNAL_GIO_UNIX_CFLAGS)
+       -I$(srcdir)/src/ostree -I$(srcdir)/libglnx $(OT_INTERNAL_GIO_UNIX_CFLAGS) \
+       -DPKGLIBEXECDIR=\"$(pkglibexecdir)\"
 ostree_bin_shared_ldadd = $(AM_LDFLAGS) libglnx.la libotutil.la libostree-1.la \
        $(OT_INTERNAL_GIO_UNIX_LIBS)
 
@@ -112,12 +113,14 @@ ostree_LDADD = $(ostree_bin_shared_ldadd) libbsdiff.la libostree-kernel-args.la
 
 
 if USE_LIBSOUP
-ostree_SOURCES += \
-       src/ostree/ot-builtin-pull.c \
-       src/ostree/ot-builtin-trivial-httpd.c \
-       $(NULL)
+ostree_SOURCES += src/ostree/ot-builtin-pull.c src/ostree/ot-builtin-trivial-httpd.c
 ostree_CFLAGS += $(OT_INTERNAL_SOUP_CFLAGS)
 ostree_LDADD += $(OT_INTERNAL_SOUP_LIBS)
+
+pkglibexec_PROGRAMS += ostree-trivial-httpd
+ostree_trivial_httpd_SOURCES = src/ostree/ostree-trivial-httpd.c
+ostree_trivial_httpd_CFLAGS = $(ostree_bin_shared_cflags) $(OT_INTERNAL_SOUP_CFLAGS)
+ostree_trivial_httpd_LDADD = $(ostree_bin_shared_ldadd) $(OT_INTERNAL_SOUP_LIBS)
 endif
 
 if USE_LIBARCHIVE
index 1f9cad48b5bccc5f25b37e3b9062855a2c6646f1..63ceea1eb15c311b845e7886ff94403bae59a40d 100644 (file)
@@ -28,6 +28,7 @@ EXTRA_DIST += \
 # include the builddir in $PATH so we find our just-built ostree
 # binary.
 TESTS_ENVIRONMENT += OT_TESTS_DEBUG=1 \
+       OSTREE_UNINSTALLED=$(abs_top_builddir) \
        G_DEBUG=fatal-warnings \
        GI_TYPELIB_PATH=$$(cd $(top_builddir) && pwd)$${GI_TYPELIB_PATH:+:$$GI_TYPELIB_PATH} \
        LD_LIBRARY_PATH=$$(cd $(top_builddir)/.libs && pwd)$${LD_LIBRARY_PATH:+:$${LD_LIBRARY_PATH}} \
diff --git a/src/ostree/ostree-trivial-httpd.c b/src/ostree/ostree-trivial-httpd.c
new file mode 100644 (file)
index 0000000..ef297af
--- /dev/null
@@ -0,0 +1,683 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011,2013 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <libsoup/soup.h>
+
+#include <gio/gunixoutputstream.h>
+
+#include "ot-main.h"
+#include "ot-builtins.h"
+#include "ostree.h"
+#include "otutil.h"
+
+#include <locale.h>
+#include <sys/socket.h>
+#include <sys/prctl.h>
+#include <signal.h>
+
+static char *opt_port_file = NULL;
+static char *opt_log = NULL;
+static gboolean opt_daemonize;
+static gboolean opt_autoexit;
+static gboolean opt_force_ranges;
+static int opt_random_500s_percentage;
+/* We have a strong upper bound for any unlikely
+ * cases involving repeated random 500s. */
+static int opt_random_500s_max = 100;
+static gint opt_port = 0;
+static gchar **opt_expected_cookies;
+static gchar **opt_expected_headers;
+
+static guint emitted_random_500s_count = 0;
+
+typedef struct {
+  int root_dfd;
+  gboolean running;
+  GOutputStream *log;
+} OtTrivialHttpd;
+
+static GOptionEntry options[] = {
+  { "daemonize", 'd', 0, G_OPTION_ARG_NONE, &opt_daemonize, "Fork into background when ready", NULL },
+  { "autoexit", 0, 0, G_OPTION_ARG_NONE, &opt_autoexit, "Automatically exit when directory is deleted", NULL },
+  { "port", 'P', 0, G_OPTION_ARG_INT, &opt_port, "Use the specified TCP port", NULL },
+  { "port-file", 'p', 0, G_OPTION_ARG_FILENAME, &opt_port_file, "Write port number to PATH (- for standard output)", "PATH" },
+  { "force-range-requests", 0, 0, G_OPTION_ARG_NONE, &opt_force_ranges, "Force range requests by only serving half of files", NULL },
+  { "random-500s", 0, 0, G_OPTION_ARG_INT, &opt_random_500s_percentage, "Generate random HTTP 500 errors approximately for PERCENTAGE requests", "PERCENTAGE" },
+  { "random-500s-max", 0, 0, G_OPTION_ARG_INT, &opt_random_500s_max, "Limit HTTP 500 errors to MAX (default 100)", "MAX" },
+  { "log-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_log, "Put logs here", "PATH" },
+  { "expected-cookies", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_expected_cookies, "Expect given cookies in the http request", "KEY=VALUE" },
+  { "expected-header", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_expected_headers, "Expect given headers in the http request", "KEY=VALUE" },
+  { NULL }
+};
+
+static void
+httpd_log (OtTrivialHttpd *httpd, const gchar *format, ...) __attribute__ ((format(printf, 2, 3)));
+
+static void
+httpd_log (OtTrivialHttpd *httpd, const gchar *format, ...)
+{
+  g_autoptr(GString) str = NULL;
+  va_list args;
+  gsize written;
+
+  if (!httpd->log)
+    return;
+
+  {
+    g_autoptr(GDateTime) now = g_date_time_new_now_local ();
+    g_autofree char *timestamp = g_date_time_format (now, "%F %T");
+    str = g_string_new (timestamp);
+    g_string_append_printf (str, ".%06d - ", g_date_time_get_microsecond (now));
+  }
+
+  va_start (args, format);
+  g_string_append_vprintf (str, format, args);
+  va_end (args);
+
+  g_output_stream_write_all (httpd->log, str->str, str->len, &written, NULL, NULL);
+}
+
+static int
+compare_strings (gconstpointer a, gconstpointer b)
+{
+  const char **sa = (const char **)a;
+  const char **sb = (const char **)b;
+
+  return strcmp (*sa, *sb);
+}
+
+static GString *
+get_directory_listing (int dfd,
+                       const char *path)
+{
+  g_autoptr(GPtrArray) entries = g_ptr_array_new_with_free_func (g_free);
+  g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
+  g_autoptr(GError) local_error = NULL;
+  GError **error = &local_error;
+  guint i;
+  char *escaped;
+  GString *listing;
+
+  listing = g_string_new ("<html>\r\n");
+
+  if (!glnx_dirfd_iterator_init_at (dfd, path, FALSE, &dfd_iter, error))
+    goto out;
+
+  while (TRUE)
+    {
+      struct dirent *dent;
+
+      if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, NULL, error))
+        goto out;
+
+      if (dent == NULL)
+        break;
+
+      escaped = g_markup_escape_text (dent->d_name, -1);
+      g_ptr_array_add (entries, escaped);
+    }
+
+  g_ptr_array_sort (entries, (GCompareFunc)compare_strings);
+
+  escaped = g_markup_escape_text (strchr (path, '/'), -1);
+  g_string_append_printf (listing, "<head><title>Index of %s</title></head>\r\n", escaped);
+  g_string_append_printf (listing, "<body><h1>Index of %s</h1>\r\n<p>\r\n", escaped);
+  g_free (escaped);
+  for (i = 0; i < entries->len; i++)
+    {
+      g_string_append_printf (listing, "<a href=\"%s\">%s</a><br>\r\n",
+                              (char *)entries->pdata[i], 
+                              (char *)entries->pdata[i]);
+      g_free (g_steal_pointer (&entries->pdata[i]));
+    }
+  g_string_append (listing, "</body>\r\n</html>\r\n");
+ out:
+  if (local_error)
+    g_printerr ("%s\n", local_error->message);
+  return listing;
+}
+
+/* Only allow reading files that have o+r, and for directories, o+x.
+ * This makes this server relatively safe to use on multiuser
+ * machines.
+ */
+static gboolean
+is_safe_to_access (struct stat *stbuf)
+{
+  /* Only regular files or directores */
+  if (!(S_ISREG (stbuf->st_mode) || S_ISDIR (stbuf->st_mode)))
+    return FALSE;
+  /* Must be o+r */
+  if (!(stbuf->st_mode & S_IROTH))
+    return FALSE;
+  /* For directories, must be o+x */
+  if (S_ISDIR (stbuf->st_mode) && !(stbuf->st_mode & S_IXOTH))
+    return FALSE;
+  return TRUE;
+}
+
+static void
+close_socket (SoupMessage *msg, gpointer user_data)
+{
+  SoupSocket *sock = user_data;
+  int sockfd;
+
+  /* Actually calling soup_socket_disconnect() here would cause
+   * us to leak memory, so just shutdown the socket instead.
+   */
+  sockfd = soup_socket_get_fd (sock);
+#ifdef G_OS_WIN32
+  shutdown (sockfd, SD_SEND);
+#else
+  shutdown (sockfd, SHUT_WR);
+#endif
+}
+
+static void
+do_get (OtTrivialHttpd    *self,
+        SoupServer        *server,
+        SoupMessage       *msg,
+        const char        *path,
+        SoupClientContext *context)
+{
+  char *slash;
+  int ret;
+  struct stat stbuf;
+
+  httpd_log (self, "serving %s\n", path);
+
+  if (opt_expected_cookies)
+    {
+      GSList *cookies = soup_cookies_from_request (msg);
+      GSList *l;
+      int i;
+
+      for (i = 0 ; opt_expected_cookies[i] != NULL; i++)
+        {
+          gboolean found = FALSE;
+          gchar *k = opt_expected_cookies[i];
+          gchar *v = strchr (k, '=') + 1;
+
+          for (l = cookies;  l != NULL ; l = g_slist_next (l))
+            {
+              SoupCookie *c = l->data;
+
+              if (!strncmp (k, soup_cookie_get_name (c), v - k - 1) &&
+                  !strcmp (v, soup_cookie_get_value (c)))
+                {
+                  found = TRUE;
+                  break;
+                }
+            }
+
+          if (!found)
+            {
+              httpd_log (self, "Expected cookie not found %s\n", k);
+              soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+              soup_cookies_free (cookies);
+              goto out;
+            }
+        }
+      soup_cookies_free (cookies);
+    }
+
+  if (opt_expected_headers)
+    {
+      for (int i = 0 ; opt_expected_headers[i] != NULL; i++)
+        {
+          const gchar *kv = opt_expected_headers[i];
+          const gchar *eq = strchr (kv, '=');
+
+          g_assert (eq);
+
+          {
+            g_autofree char *k = g_strndup (kv, eq - kv);
+            const gchar *expected_v = eq + 1;
+            const gchar *found_v = soup_message_headers_get_one (msg->request_headers, k);
+
+            if (!found_v)
+              {
+                httpd_log (self, "Expected header not found %s\n", k);
+                soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+                goto out;
+              }
+            if (strcmp (found_v, expected_v) != 0)
+              {
+                httpd_log (self, "Expected header %s: %s but found %s\n", k, expected_v, found_v);
+                soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+                goto out;
+              }
+          }
+        }
+    }
+
+  if (strstr (path, "../") != NULL)
+    {
+      soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+      goto out;
+    }
+
+  if (opt_random_500s_percentage > 0 &&
+      emitted_random_500s_count < opt_random_500s_max &&
+      g_random_int_range (0, 100) < opt_random_500s_percentage)
+    {
+      emitted_random_500s_count++;
+      soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+      goto out;
+    }
+
+  while (path[0] == '/')
+    path++;
+
+  do
+    ret = fstatat (self->root_dfd, path, &stbuf, 0);
+  while (ret == -1 && errno == EINTR);
+  if (ret == -1)
+    {
+      if (errno == EPERM)
+        soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+      else if (errno == ENOENT)
+        soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
+      else
+        soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+      goto out;
+    }
+
+  if (!is_safe_to_access (&stbuf))
+    {
+      soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+      goto out;
+    }
+
+  if (S_ISDIR (stbuf.st_mode))
+    {
+      slash = strrchr (path, '/');
+      if (!slash || slash[1])
+        {
+          g_autofree char *redir_uri = NULL;
+
+          redir_uri = g_strdup_printf ("%s/", soup_message_get_uri (msg)->path);
+          soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY,
+                                     redir_uri);
+        }
+      else
+        {
+          g_autofree char *index_realpath = g_strconcat (path, "/index.html", NULL);
+          if (fstatat (self->root_dfd, index_realpath, &stbuf, 0) != -1)
+            {
+              g_autofree char *index_path = g_strconcat (path, "/index.html", NULL);
+              do_get (self, server, msg, index_path, context);
+            }
+          else
+            {
+              GString *listing = get_directory_listing (self->root_dfd, path);
+              soup_message_set_response (msg, "text/html",
+                                         SOUP_MEMORY_TAKE,
+                                         listing->str, listing->len);
+              soup_message_set_status (msg, SOUP_STATUS_OK);
+              g_string_free (listing, FALSE);
+            }
+        }
+    }
+  else 
+    {
+      if (!S_ISREG (stbuf.st_mode))
+        {
+          soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+          goto out;
+        }
+      
+      if (msg->method == SOUP_METHOD_GET)
+        {
+          glnx_fd_close int fd = -1;
+          g_autoptr(GMappedFile) mapping = NULL;
+          gsize buffer_length, file_size;
+          SoupRange *ranges;
+          int ranges_length;
+          gboolean have_ranges;
+
+          fd = openat (self->root_dfd, path, O_RDONLY | O_CLOEXEC);
+          if (fd < 0)
+            {
+              soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+              goto out;
+            }
+
+          mapping = g_mapped_file_new_from_fd (fd, FALSE, NULL);
+          if (!mapping)
+            {
+              soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+              goto out;
+            }
+          (void) close (fd); fd = -1;
+
+          file_size = g_mapped_file_get_length (mapping);
+          have_ranges = soup_message_headers_get_ranges(msg->request_headers, file_size, &ranges, &ranges_length);
+          if (opt_force_ranges && !have_ranges && g_strrstr (path, "/objects") != NULL)
+            {
+              SoupSocket *sock;
+              buffer_length = file_size/2;
+              soup_message_headers_set_content_length (msg->response_headers, file_size);
+              soup_message_headers_append (msg->response_headers,
+                                           "Connection", "close");
+
+              /* soup-message-io will wait for us to add
+               * another chunk after the first, to fill out
+               * the declared Content-Length. Instead, we
+               * forcibly close the socket at that point.
+               */
+              sock = soup_client_context_get_socket (context);
+              g_signal_connect (msg, "wrote-chunk", G_CALLBACK (close_socket), sock);
+            }
+          else
+            buffer_length = file_size;
+
+          if (have_ranges)
+            {
+              if (ranges_length > 0 && ranges[0].start >= file_size)
+                {
+                  soup_message_set_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
+                  soup_message_headers_free_ranges (msg->request_headers, ranges);
+                  goto out;
+                }
+              soup_message_headers_free_ranges (msg->request_headers, ranges);
+            }
+          if (buffer_length > 0)
+            {
+              SoupBuffer *buffer;
+
+              buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping),
+                                                   buffer_length,
+                                                   g_mapped_file_ref (mapping),
+                                                   (GDestroyNotify)g_mapped_file_unref);
+              soup_message_body_append_buffer (msg->response_body, buffer);
+              soup_buffer_free (buffer);
+            }
+        }
+      else /* msg->method == SOUP_METHOD_HEAD */
+        {
+          g_autofree char *length = NULL;
+
+          /* We could just use the same code for both GET and
+           * HEAD (soup-message-server-io.c will fix things up).
+           * But we'll optimize and avoid the extra I/O.
+           */
+          length = g_strdup_printf ("%lu", (gulong)stbuf.st_size);
+          soup_message_headers_append (msg->response_headers,
+                                       "Content-Length", length);
+        }
+      soup_message_set_status (msg, SOUP_STATUS_OK);
+    }
+ out:
+  {
+    guint status = 0;
+    g_autofree gchar *reason = NULL;
+
+    g_object_get (msg,
+                  "status-code", &status,
+                  "reason-phrase", &reason,
+                  NULL);
+    httpd_log (self, "  status: %s (%u)\n", reason, status);
+  }
+  return;
+}
+
+static void
+httpd_callback (SoupServer *server, SoupMessage *msg,
+                const char *path, GHashTable *query,
+                SoupClientContext *context, gpointer data)
+{
+  OtTrivialHttpd *self = data;
+
+  if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD)
+    do_get (self, server, msg, path, context);
+  else
+    soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
+}
+
+static void
+on_dir_changed (GFileMonitor  *mon,
+               GFile *file,
+               GFile *other,
+               GFileMonitorEvent  event,
+               gpointer user_data)
+{
+  OtTrivialHttpd *self = user_data;
+
+  if (event == G_FILE_MONITOR_EVENT_DELETED)
+    {
+      self->running = FALSE;
+      g_main_context_wakeup (NULL);
+    }
+}
+
+static gboolean
+run (int argc, char **argv, GCancellable *cancellable, GError **error)
+{
+  gboolean ret = FALSE;
+  g_autoptr(GOptionContext) context = NULL;
+  const char *dirpath;
+  OtTrivialHttpd appstruct = { 0, };
+  OtTrivialHttpd *app = &appstruct;
+  glnx_unref_object SoupServer *server = NULL;
+  g_autoptr(GFileMonitor) dirmon = NULL;
+
+  context = g_option_context_new ("[DIR] - Simple webserver");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  app->root_dfd = -1;
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (argc > 1)
+    dirpath = argv[1];
+  else
+    dirpath = ".";
+
+  if (!glnx_opendirat (AT_FDCWD, dirpath, TRUE, &app->root_dfd, error))
+    goto out;
+
+  if (!(opt_random_500s_percentage >= 0 && opt_random_500s_percentage <= 99))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid --random-500s=%u", opt_random_500s_percentage);
+      goto out;
+    }
+
+  if (opt_log)
+    {
+      GOutputStream *stream = NULL;
+
+      if (g_strcmp0 (opt_log, "-") == 0)
+        {
+          if (opt_daemonize)
+            {
+              ot_util_usage_error (context, "Cannot use --log-file=- and --daemonize at the same time", error);
+              goto out;
+            }
+          stream = G_OUTPUT_STREAM (g_unix_output_stream_new (STDOUT_FILENO, FALSE));
+        }
+      else
+        {
+          g_autoptr(GFile) log_file;
+          GFileOutputStream* log_stream;
+
+          log_file = g_file_new_for_path (opt_log);
+          log_stream = g_file_create (log_file,
+                                      G_FILE_CREATE_PRIVATE,
+                                      cancellable,
+                                      error);
+          if (!log_stream)
+            goto out;
+          stream = G_OUTPUT_STREAM (log_stream);
+        }
+
+      app->log = stream;
+    }
+
+#if SOUP_CHECK_VERSION(2, 48, 0)
+  server = soup_server_new (SOUP_SERVER_SERVER_HEADER, "ostree-httpd ", NULL);
+  if (!soup_server_listen_all (server, opt_port, 0, error))
+    goto out;
+#else
+  server = soup_server_new (SOUP_SERVER_PORT, opt_port,
+                            SOUP_SERVER_SERVER_HEADER, "ostree-httpd ",
+                            NULL);
+#endif
+
+  soup_server_add_handler (server, NULL, httpd_callback, app, NULL);
+  if (opt_port_file)
+    {
+      g_autofree char *portstr = NULL;
+#if SOUP_CHECK_VERSION(2, 48, 0)
+      GSList *listeners = soup_server_get_listeners (server);
+      g_autoptr(GSocket) listener = NULL;
+      g_autoptr(GSocketAddress) addr = NULL;
+      
+      g_assert (listeners);
+      listener = g_object_ref (listeners->data);
+      g_slist_free (listeners);
+      listeners = NULL;
+      addr = g_socket_get_local_address (listener, error);
+      if (!addr)
+        goto out;
+
+      g_assert (G_IS_INET_SOCKET_ADDRESS (addr));
+      
+      portstr = g_strdup_printf ("%u\n", g_inet_socket_address_get_port ((GInetSocketAddress*)addr));
+#else
+      portstr = g_strdup_printf ("%u\n", soup_server_get_port (server));
+#endif
+
+      if (g_strcmp0 ("-", opt_port_file) == 0)
+        {
+          fputs (portstr, stdout); // not g_print - this must go to stdout, not a handler
+          fflush (stdout);
+        }
+      else if (!g_file_set_contents (opt_port_file, portstr, strlen (portstr), error))
+        goto out;
+    }
+#if !SOUP_CHECK_VERSION(2, 48, 0)
+  soup_server_run_async (server);
+#endif
+  
+  if (opt_daemonize)
+    {
+      pid_t pid = fork();
+      if (pid == -1)
+        {
+          int errsv = errno;
+          g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                               g_strerror (errsv));
+          goto out;
+        }
+      else if (pid > 0)
+        {
+          ret = TRUE;
+          goto out;
+        }
+      /* Child, continue */
+      /* Daemonising: close stdout/stderr so $() et al work on us */
+      fclose (stdout);
+      fclose (stdin);
+    }
+  else
+    {
+      /* Since we're used for testing purposes, let's just do this by
+       * default.  This ensures we exit when our parent does.
+       */
+      if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0)
+        {
+          if (errno != ENOSYS)
+            {
+              glnx_set_error_from_errno (error);
+              goto out;
+            }
+        }
+    }
+
+  app->running = TRUE;
+  if (opt_autoexit)
+    {
+      gboolean is_symlink = FALSE;
+      g_autoptr(GFile) root = NULL;
+      g_autoptr(GFileInfo) info = NULL;
+
+      root = g_file_new_for_path (dirpath);
+      info = g_file_query_info (root,
+                                G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
+                                G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                cancellable, error);
+      if (!info)
+        goto out;
+
+      is_symlink = g_file_info_get_is_symlink (info);
+
+      if (is_symlink)
+        dirmon = g_file_monitor_file (root, 0, cancellable, error);
+      else
+        dirmon = g_file_monitor_directory (root, 0, cancellable, error);
+
+      if (!dirmon)
+        goto out;
+      g_signal_connect (dirmon, "changed", G_CALLBACK (on_dir_changed), app);
+    }
+  httpd_log (app, "serving at root %s\n", dirpath);
+  while (app->running)
+    g_main_context_iteration (NULL, TRUE);
+
+  ret = TRUE;
+ out:
+  if (app->root_dfd != -1)
+    (void) close (app->root_dfd);
+  g_clear_object (&app->log);
+  return ret;
+}
+
+int
+main (int    argc,
+      char **argv)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GCancellable) cancellable = NULL;
+
+  setlocale (LC_ALL, "");
+
+  g_set_prgname (argv[0]);
+
+  if (!run (argc, argv, cancellable, &error))
+    {
+      int is_tty = isatty (1);
+      const char *prefix = "";
+      const char *suffix = "";
+      if (is_tty)
+        {
+          prefix = "\x1b[31m\x1b[1m"; /* red, bold */
+          suffix = "\x1b[22m\x1b[0m"; /* bold off, color reset */
+        }
+      g_printerr ("%serror: %s%s\n", prefix, suffix, error->message);
+      return 1;
+    }
+
+  return 0;
+}
index 0a5538589594453d4a2fc33d467eb5b6d2a64a53..206970c56d6389d7866e5abe5037e1270a655722 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
  *
- * Copyright (C) 2011,2013 Colin Walters <walters@verbum.org>
+ * Copyright (C) 2016 Colin Walters <walters@verbum.org>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
 
 #include "config.h"
 
-#include <libsoup/soup.h>
-
-#include <gio/gunixoutputstream.h>
-
 #include "ot-main.h"
 #include "ot-builtins.h"
 #include "ostree.h"
 #include "otutil.h"
 
-#include <sys/socket.h>
-#include <sys/prctl.h>
-#include <signal.h>
-
-static char *opt_port_file = NULL;
-static char *opt_log = NULL;
-static gboolean opt_daemonize;
-static gboolean opt_autoexit;
-static gboolean opt_force_ranges;
-static int opt_random_500s_percentage;
-/* We have a strong upper bound for any unlikely
- * cases involving repeated random 500s. */
-static int opt_random_500s_max = 100;
-static gint opt_port = 0;
-static gchar **opt_expected_cookies;
-static gchar **opt_expected_headers;
-
-static guint emitted_random_500s_count = 0;
-
-typedef struct {
-  int root_dfd;
-  gboolean running;
-  GOutputStream *log;
-} OtTrivialHttpd;
-
-static GOptionEntry options[] = {
-  { "daemonize", 'd', 0, G_OPTION_ARG_NONE, &opt_daemonize, "Fork into background when ready", NULL },
-  { "autoexit", 0, 0, G_OPTION_ARG_NONE, &opt_autoexit, "Automatically exit when directory is deleted", NULL },
-  { "port", 'P', 0, G_OPTION_ARG_INT, &opt_port, "Use the specified TCP port", NULL },
-  { "port-file", 'p', 0, G_OPTION_ARG_FILENAME, &opt_port_file, "Write port number to PATH (- for standard output)", "PATH" },
-  { "force-range-requests", 0, 0, G_OPTION_ARG_NONE, &opt_force_ranges, "Force range requests by only serving half of files", NULL },
-  { "random-500s", 0, 0, G_OPTION_ARG_INT, &opt_random_500s_percentage, "Generate random HTTP 500 errors approximately for PERCENTAGE requests", "PERCENTAGE" },
-  { "random-500s-max", 0, 0, G_OPTION_ARG_INT, &opt_random_500s_max, "Limit HTTP 500 errors to MAX (default 100)", "MAX" },
-  { "log-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_log, "Put logs here", "PATH" },
-  { "expected-cookies", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_expected_cookies, "Expect given cookies in the http request", "KEY=VALUE" },
-  { "expected-header", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_expected_headers, "Expect given headers in the http request", "KEY=VALUE" },
-  { NULL }
-};
-
-static void
-httpd_log (OtTrivialHttpd *httpd, const gchar *format, ...) __attribute__ ((format(printf, 2, 3)));
-
-static void
-httpd_log (OtTrivialHttpd *httpd, const gchar *format, ...)
-{
-  g_autoptr(GString) str = NULL;
-  va_list args;
-  gsize written;
-
-  if (!httpd->log)
-    return;
-
-  {
-    g_autoptr(GDateTime) now = g_date_time_new_now_local ();
-    g_autofree char *timestamp = g_date_time_format (now, "%F %T");
-    str = g_string_new (timestamp);
-    g_string_append_printf (str, ".%06d - ", g_date_time_get_microsecond (now));
-  }
-
-  va_start (args, format);
-  g_string_append_vprintf (str, format, args);
-  va_end (args);
-
-  g_output_stream_write_all (httpd->log, str->str, str->len, &written, NULL, NULL);
-}
-
-static int
-compare_strings (gconstpointer a, gconstpointer b)
-{
-  const char **sa = (const char **)a;
-  const char **sb = (const char **)b;
-
-  return strcmp (*sa, *sb);
-}
-
-static GString *
-get_directory_listing (int dfd,
-                       const char *path)
-{
-  g_autoptr(GPtrArray) entries = g_ptr_array_new_with_free_func (g_free);
-  g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
-  g_autoptr(GError) local_error = NULL;
-  GError **error = &local_error;
-  guint i;
-  char *escaped;
-  GString *listing;
-
-  listing = g_string_new ("<html>\r\n");
-
-  if (!glnx_dirfd_iterator_init_at (dfd, path, FALSE, &dfd_iter, error))
-    goto out;
-
-  while (TRUE)
-    {
-      struct dirent *dent;
-
-      if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, NULL, error))
-        goto out;
-
-      if (dent == NULL)
-        break;
-
-      escaped = g_markup_escape_text (dent->d_name, -1);
-      g_ptr_array_add (entries, escaped);
-    }
-
-  g_ptr_array_sort (entries, (GCompareFunc)compare_strings);
-
-  escaped = g_markup_escape_text (strchr (path, '/'), -1);
-  g_string_append_printf (listing, "<head><title>Index of %s</title></head>\r\n", escaped);
-  g_string_append_printf (listing, "<body><h1>Index of %s</h1>\r\n<p>\r\n", escaped);
-  g_free (escaped);
-  for (i = 0; i < entries->len; i++)
-    {
-      g_string_append_printf (listing, "<a href=\"%s\">%s</a><br>\r\n",
-                              (char *)entries->pdata[i], 
-                              (char *)entries->pdata[i]);
-      g_free (g_steal_pointer (&entries->pdata[i]));
-    }
-  g_string_append (listing, "</body>\r\n</html>\r\n");
- out:
-  if (local_error)
-    g_printerr ("%s\n", local_error->message);
-  return listing;
-}
-
-/* Only allow reading files that have o+r, and for directories, o+x.
- * This makes this server relatively safe to use on multiuser
- * machines.
- */
-static gboolean
-is_safe_to_access (struct stat *stbuf)
-{
-  /* Only regular files or directores */
-  if (!(S_ISREG (stbuf->st_mode) || S_ISDIR (stbuf->st_mode)))
-    return FALSE;
-  /* Must be o+r */
-  if (!(stbuf->st_mode & S_IROTH))
-    return FALSE;
-  /* For directories, must be o+x */
-  if (S_ISDIR (stbuf->st_mode) && !(stbuf->st_mode & S_IXOTH))
-    return FALSE;
-  return TRUE;
-}
-
-static void
-close_socket (SoupMessage *msg, gpointer user_data)
-{
-  SoupSocket *sock = user_data;
-  int sockfd;
-
-  /* Actually calling soup_socket_disconnect() here would cause
-   * us to leak memory, so just shutdown the socket instead.
-   */
-  sockfd = soup_socket_get_fd (sock);
-#ifdef G_OS_WIN32
-  shutdown (sockfd, SD_SEND);
-#else
-  shutdown (sockfd, SHUT_WR);
-#endif
-}
-
-static void
-do_get (OtTrivialHttpd    *self,
-        SoupServer        *server,
-        SoupMessage       *msg,
-        const char        *path,
-        SoupClientContext *context)
-{
-  char *slash;
-  int ret;
-  struct stat stbuf;
-
-  httpd_log (self, "serving %s\n", path);
-
-  if (opt_expected_cookies)
-    {
-      GSList *cookies = soup_cookies_from_request (msg);
-      GSList *l;
-      int i;
-
-      for (i = 0 ; opt_expected_cookies[i] != NULL; i++)
-        {
-          gboolean found = FALSE;
-          gchar *k = opt_expected_cookies[i];
-          gchar *v = strchr (k, '=') + 1;
-
-          for (l = cookies;  l != NULL ; l = g_slist_next (l))
-            {
-              SoupCookie *c = l->data;
-
-              if (!strncmp (k, soup_cookie_get_name (c), v - k - 1) &&
-                  !strcmp (v, soup_cookie_get_value (c)))
-                {
-                  found = TRUE;
-                  break;
-                }
-            }
-
-          if (!found)
-            {
-              httpd_log (self, "Expected cookie not found %s\n", k);
-              soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
-              soup_cookies_free (cookies);
-              goto out;
-            }
-        }
-      soup_cookies_free (cookies);
-    }
-
-  if (opt_expected_headers)
-    {
-      for (int i = 0 ; opt_expected_headers[i] != NULL; i++)
-        {
-          const gchar *kv = opt_expected_headers[i];
-          const gchar *eq = strchr (kv, '=');
-
-          g_assert (eq);
-
-          {
-            g_autofree char *k = g_strndup (kv, eq - kv);
-            const gchar *expected_v = eq + 1;
-            const gchar *found_v = soup_message_headers_get_one (msg->request_headers, k);
-
-            if (!found_v)
-              {
-                httpd_log (self, "Expected header not found %s\n", k);
-                soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
-                goto out;
-              }
-            if (strcmp (found_v, expected_v) != 0)
-              {
-                httpd_log (self, "Expected header %s: %s but found %s\n", k, expected_v, found_v);
-                soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
-                goto out;
-              }
-          }
-        }
-    }
-
-  if (strstr (path, "../") != NULL)
-    {
-      soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
-      goto out;
-    }
-
-  if (opt_random_500s_percentage > 0 &&
-      emitted_random_500s_count < opt_random_500s_max &&
-      g_random_int_range (0, 100) < opt_random_500s_percentage)
-    {
-      emitted_random_500s_count++;
-      soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
-      goto out;
-    }
-
-  while (path[0] == '/')
-    path++;
-
-  do
-    ret = fstatat (self->root_dfd, path, &stbuf, 0);
-  while (ret == -1 && errno == EINTR);
-  if (ret == -1)
-    {
-      if (errno == EPERM)
-        soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
-      else if (errno == ENOENT)
-        soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
-      else
-        soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
-      goto out;
-    }
-
-  if (!is_safe_to_access (&stbuf))
-    {
-      soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
-      goto out;
-    }
-
-  if (S_ISDIR (stbuf.st_mode))
-    {
-      slash = strrchr (path, '/');
-      if (!slash || slash[1])
-        {
-          g_autofree char *redir_uri = NULL;
-
-          redir_uri = g_strdup_printf ("%s/", soup_message_get_uri (msg)->path);
-          soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY,
-                                     redir_uri);
-        }
-      else
-        {
-          g_autofree char *index_realpath = g_strconcat (path, "/index.html", NULL);
-          if (fstatat (self->root_dfd, index_realpath, &stbuf, 0) != -1)
-            {
-              g_autofree char *index_path = g_strconcat (path, "/index.html", NULL);
-              do_get (self, server, msg, index_path, context);
-            }
-          else
-            {
-              GString *listing = get_directory_listing (self->root_dfd, path);
-              soup_message_set_response (msg, "text/html",
-                                         SOUP_MEMORY_TAKE,
-                                         listing->str, listing->len);
-              soup_message_set_status (msg, SOUP_STATUS_OK);
-              g_string_free (listing, FALSE);
-            }
-        }
-    }
-  else 
-    {
-      if (!S_ISREG (stbuf.st_mode))
-        {
-          soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
-          goto out;
-        }
-      
-      if (msg->method == SOUP_METHOD_GET)
-        {
-          glnx_fd_close int fd = -1;
-          g_autoptr(GMappedFile) mapping = NULL;
-          gsize buffer_length, file_size;
-          SoupRange *ranges;
-          int ranges_length;
-          gboolean have_ranges;
-
-          fd = openat (self->root_dfd, path, O_RDONLY | O_CLOEXEC);
-          if (fd < 0)
-            {
-              soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
-              goto out;
-            }
-
-          mapping = g_mapped_file_new_from_fd (fd, FALSE, NULL);
-          if (!mapping)
-            {
-              soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
-              goto out;
-            }
-          (void) close (fd); fd = -1;
-
-          file_size = g_mapped_file_get_length (mapping);
-          have_ranges = soup_message_headers_get_ranges(msg->request_headers, file_size, &ranges, &ranges_length);
-          if (opt_force_ranges && !have_ranges && g_strrstr (path, "/objects") != NULL)
-            {
-              SoupSocket *sock;
-              buffer_length = file_size/2;
-              soup_message_headers_set_content_length (msg->response_headers, file_size);
-              soup_message_headers_append (msg->response_headers,
-                                           "Connection", "close");
-
-              /* soup-message-io will wait for us to add
-               * another chunk after the first, to fill out
-               * the declared Content-Length. Instead, we
-               * forcibly close the socket at that point.
-               */
-              sock = soup_client_context_get_socket (context);
-              g_signal_connect (msg, "wrote-chunk", G_CALLBACK (close_socket), sock);
-            }
-          else
-            buffer_length = file_size;
-
-          if (have_ranges)
-            {
-              if (ranges_length > 0 && ranges[0].start >= file_size)
-                {
-                  soup_message_set_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
-                  soup_message_headers_free_ranges (msg->request_headers, ranges);
-                  goto out;
-                }
-              soup_message_headers_free_ranges (msg->request_headers, ranges);
-            }
-          if (buffer_length > 0)
-            {
-              SoupBuffer *buffer;
-
-              buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping),
-                                                   buffer_length,
-                                                   g_mapped_file_ref (mapping),
-                                                   (GDestroyNotify)g_mapped_file_unref);
-              soup_message_body_append_buffer (msg->response_body, buffer);
-              soup_buffer_free (buffer);
-            }
-        }
-      else /* msg->method == SOUP_METHOD_HEAD */
-        {
-          g_autofree char *length = NULL;
-
-          /* We could just use the same code for both GET and
-           * HEAD (soup-message-server-io.c will fix things up).
-           * But we'll optimize and avoid the extra I/O.
-           */
-          length = g_strdup_printf ("%lu", (gulong)stbuf.st_size);
-          soup_message_headers_append (msg->response_headers,
-                                       "Content-Length", length);
-        }
-      soup_message_set_status (msg, SOUP_STATUS_OK);
-    }
- out:
-  {
-    guint status = 0;
-    g_autofree gchar *reason = NULL;
-
-    g_object_get (msg,
-                  "status-code", &status,
-                  "reason-phrase", &reason,
-                  NULL);
-    httpd_log (self, "  status: %s (%u)\n", reason, status);
-  }
-  return;
-}
-
-static void
-httpd_callback (SoupServer *server, SoupMessage *msg,
-                const char *path, GHashTable *query,
-                SoupClientContext *context, gpointer data)
-{
-  OtTrivialHttpd *self = data;
-
-  if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD)
-    do_get (self, server, msg, path, context);
-  else
-    soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
-}
-
-static void
-on_dir_changed (GFileMonitor  *mon,
-               GFile *file,
-               GFile *other,
-               GFileMonitorEvent  event,
-               gpointer user_data)
-{
-  OtTrivialHttpd *self = user_data;
-
-  if (event == G_FILE_MONITOR_EVENT_DELETED)
-    {
-      self->running = FALSE;
-      g_main_context_wakeup (NULL);
-    }
-}
-
 gboolean
 ostree_builtin_trivial_httpd (int argc, char **argv, GCancellable *cancellable, GError **error)
 {
-  gboolean ret = FALSE;
-  g_autoptr(GOptionContext) context = NULL;
-  const char *dirpath;
-  OtTrivialHttpd appstruct = { 0, };
-  OtTrivialHttpd *app = &appstruct;
-  glnx_unref_object SoupServer *server = NULL;
-  g_autoptr(GFileMonitor) dirmon = NULL;
-
-  context = g_option_context_new ("[DIR] - Simple webserver");
-
-  app->root_dfd = -1;
-
-  if (!ostree_option_context_parse (context, options, &argc, &argv, OSTREE_BUILTIN_FLAG_NO_REPO, NULL, cancellable, error))
-    goto out;
-
-  if (argc > 1)
-    dirpath = argv[1];
-  else
-    dirpath = ".";
-
-  if (!glnx_opendirat (AT_FDCWD, dirpath, TRUE, &app->root_dfd, error))
-    goto out;
-
-  if (!(opt_random_500s_percentage >= 0 && opt_random_500s_percentage <= 99))
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Invalid --random-500s=%u", opt_random_500s_percentage);
-      goto out;
-    }
-
-  if (opt_log)
-    {
-      GOutputStream *stream = NULL;
-
-      if (g_strcmp0 (opt_log, "-") == 0)
-        {
-          if (opt_daemonize)
-            {
-              ot_util_usage_error (context, "Cannot use --log-file=- and --daemonize at the same time", error);
-              goto out;
-            }
-          stream = G_OUTPUT_STREAM (g_unix_output_stream_new (STDOUT_FILENO, FALSE));
-        }
-      else
-        {
-          g_autoptr(GFile) log_file;
-          GFileOutputStream* log_stream;
-
-          log_file = g_file_new_for_path (opt_log);
-          log_stream = g_file_create (log_file,
-                                      G_FILE_CREATE_PRIVATE,
-                                      cancellable,
-                                      error);
-          if (!log_stream)
-            goto out;
-          stream = G_OUTPUT_STREAM (log_stream);
-        }
-
-      app->log = stream;
-    }
-
-#if SOUP_CHECK_VERSION(2, 48, 0)
-  server = soup_server_new (SOUP_SERVER_SERVER_HEADER, "ostree-httpd ", NULL);
-  if (!soup_server_listen_all (server, opt_port, 0, error))
-    goto out;
-#else
-  server = soup_server_new (SOUP_SERVER_PORT, opt_port,
-                            SOUP_SERVER_SERVER_HEADER, "ostree-httpd ",
-                            NULL);
-#endif
-
-  soup_server_add_handler (server, NULL, httpd_callback, app, NULL);
-  if (opt_port_file)
-    {
-      g_autofree char *portstr = NULL;
-#if SOUP_CHECK_VERSION(2, 48, 0)
-      GSList *listeners = soup_server_get_listeners (server);
-      g_autoptr(GSocket) listener = NULL;
-      g_autoptr(GSocketAddress) addr = NULL;
-      
-      g_assert (listeners);
-      listener = g_object_ref (listeners->data);
-      g_slist_free (listeners);
-      listeners = NULL;
-      addr = g_socket_get_local_address (listener, error);
-      if (!addr)
-        goto out;
-
-      g_assert (G_IS_INET_SOCKET_ADDRESS (addr));
-      
-      portstr = g_strdup_printf ("%u\n", g_inet_socket_address_get_port ((GInetSocketAddress*)addr));
-#else
-      portstr = g_strdup_printf ("%u\n", soup_server_get_port (server));
-#endif
-
-      if (g_strcmp0 ("-", opt_port_file) == 0)
-        {
-          fputs (portstr, stdout); // not g_print - this must go to stdout, not a handler
-          fflush (stdout);
-        }
-      else if (!g_file_set_contents (opt_port_file, portstr, strlen (portstr), error))
-        goto out;
-    }
-#if !SOUP_CHECK_VERSION(2, 48, 0)
-  soup_server_run_async (server);
-#endif
-  
-  if (opt_daemonize)
-    {
-      pid_t pid = fork();
-      if (pid == -1)
-        {
-          int errsv = errno;
-          g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
-                               g_strerror (errsv));
-          goto out;
-        }
-      else if (pid > 0)
-        {
-          ret = TRUE;
-          goto out;
-        }
-      /* Child, continue */
-      /* Daemonising: close stdout/stderr so $() et al work on us */
-      fclose (stdout);
-      fclose (stdin);
-    }
-  else
-    {
-      /* Since we're used for testing purposes, let's just do this by
-       * default.  This ensures we exit when our parent does.
-       */
-      if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0)
-        {
-          if (errno != ENOSYS)
-            {
-              glnx_set_error_from_errno (error);
-              goto out;
-            }
-        }
-    }
-
-  app->running = TRUE;
-  if (opt_autoexit)
-    {
-      gboolean is_symlink = FALSE;
-      g_autoptr(GFile) root = NULL;
-      g_autoptr(GFileInfo) info = NULL;
-
-      root = g_file_new_for_path (dirpath);
-      info = g_file_query_info (root,
-                                G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
-                                G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                cancellable, error);
-      if (!info)
-        goto out;
-
-      is_symlink = g_file_info_get_is_symlink (info);
-
-      if (is_symlink)
-        dirmon = g_file_monitor_file (root, 0, cancellable, error);
-      else
-        dirmon = g_file_monitor_directory (root, 0, cancellable, error);
-
-      if (!dirmon)
-        goto out;
-      g_signal_connect (dirmon, "changed", G_CALLBACK (on_dir_changed), app);
-    }
-  httpd_log (app, "serving at root %s\n", dirpath);
-  while (app->running)
-    g_main_context_iteration (NULL, TRUE);
+  g_autoptr(GPtrArray) new_argv = g_ptr_array_new ();
 
-  ret = TRUE;
- out:
-  if (app->root_dfd != -1)
-    (void) close (app->root_dfd);
-  g_clear_object (&app->log);
-  return ret;
+  g_ptr_array_add (new_argv, PKGLIBEXECDIR "/ostree-trivial-httpd");
+  for (int i = 1; i < argc; i++)
+    g_ptr_array_add (new_argv, argv[i]);
+  g_ptr_array_add (new_argv, NULL);
+  execvp (new_argv->pdata[0], (char**)new_argv->pdata);
+  /* Fall through on error */
+  glnx_set_error_from_errno (error);
+  return FALSE;
 }
index c0bf8d0dbafa71553cb6943adc29ffbcb88d8004..137d9534f2f34a08c263ed3b0ddd51caae796fd8 100755 (executable)
@@ -102,6 +102,12 @@ else
     fi
 fi
 
+if test -n "${OSTREE_UNINSTALLED:-}"; then
+    OSTREE_HTTPD=${OSTREE_UNINSTALLED}/ostree-trivial-httpd
+else
+    OSTREE_HTTPD="${CMD_PREFIX} ostree trivial-httpd"
+fi
+
 assert_streq () {
     test "$1" = "$2" || (echo 1>&2 "$1 != $2"; exit 1)
 }
@@ -257,7 +263,7 @@ setup_fake_remote_repo1() {
     mkdir ${test_tmpdir}/httpd
     cd httpd
     ln -s ${test_tmpdir}/ostree-srv ostree
-    ${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize -p ${test_tmpdir}/httpd-port $args
+    ${OSTREE_HTTPD} --autoexit --daemonize -p ${test_tmpdir}/httpd-port $args
     port=$(cat ${test_tmpdir}/httpd-port)
     echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
     cd ${oldpwd} 
@@ -379,7 +385,7 @@ EOF
     mkdir ${test_tmpdir}/httpd
     cd httpd
     ln -s ${test_tmpdir} ostree
-    ${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize -p ${test_tmpdir}/httpd-port
+    ${OSTREE_HTTPD} --autoexit --daemonize -p ${test_tmpdir}/httpd-port
     port=$(cat ${test_tmpdir}/httpd-port)
     echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
     cd ${oldpwd} 
index 60265c1a994a3a0561dcc9bb6e38ea7b86489deb..f963b1045b9e1cdf8785696b43eb4a30d0743126 100755 (executable)
@@ -53,7 +53,7 @@ cd ${test_tmpdir}
 mkdir ${test_tmpdir}/httpd
 cd httpd
 ln -s ${test_tmpdir}/ostree-srv ostree
-${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize -P 18081 -p ${test_tmpdir}/httpd-port
+${OSTREE_HTTPD} --autoexit --daemonize -P 18081 -p ${test_tmpdir}/httpd-port
 port=$(cat ${test_tmpdir}/httpd-port)
 assert_streq $port 18081
 echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
index 16dcbe4fc43e09b383256ffb47503be7ea867793..d74d619b3f7b28550da5278957cf5d5fa0295d28 100755 (executable)
@@ -51,8 +51,7 @@ fi
 find ${test_tmpdir}/ostree-srv/gnomerepo/objects \
   ! -name '*.commitmeta' -type f | xargs rm
 
-${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize \
-  -p ${test_tmpdir}/httpd-content-port
+${OSTREE_HTTPD} --autoexit --daemonize -p ${test_tmpdir}/httpd-content-port
 content_port=$(cat ${test_tmpdir}/httpd-content-port)
 echo "http://127.0.0.1:${content_port}" > ${test_tmpdir}/httpd-content-address
 
index 2a1a73e776abb19c6622ddb4d443f981b5c7e2ea..07d619dfe21a4033890fa936140bb75c98d4ded0 100755 (executable)
@@ -29,7 +29,7 @@ echo '1..9'
 cd ${test_tmpdir}
 mkdir metalink-data
 cd metalink-data
-${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize -p ${test_tmpdir}/metalink-httpd-port
+${OSTREE_HTTPD} --autoexit --daemonize -p ${test_tmpdir}/metalink-httpd-port
 metalink_port=$(cat ${test_tmpdir}/metalink-httpd-port)
 echo "http://127.0.0.1:${metalink_port}" > ${test_tmpdir}/metalink-httpd-address
 
index 454014ca781178661d3c4a428a704a6ecbbc9ed5..13f40e7a6bda564953941a1347f7c322bbe14b0b 100755 (executable)
@@ -33,7 +33,7 @@ setup_mirror () {
   cd $name
   cp -a ${test_tmpdir}/ostree-srv ostree
 
-  ${CMD_PREFIX} ostree trivial-httpd --autoexit --daemonize \
+  ${OSTREE_HTTPD} --autoexit --daemonize \
     -p ${test_tmpdir}/${name}-port
   port=$(cat ${test_tmpdir}/${name}-port)
   echo "http://127.0.0.1:${port}" > ${test_tmpdir}/${name}-address
index d81b34549454f3b0dc778aa08c87d0b7cee23c66..928013a5744ea910b5921aea851b4220f79d88b8 100755 (executable)
@@ -50,7 +50,7 @@ mkdir mirror-httpd
 cd mirror-httpd
 ln -s ${test_tmpdir}/mirror-srv ostree
 mirror_log="${test_tmpdir}/mirror_log"
-${CMD_PREFIX} ostree trivial-httpd --log-file=${mirror_log} --autoexit --daemonize -p ${test_tmpdir}/mirror-httpd-port
+${OSTREE_HTTPD} --log-file=${mirror_log} --autoexit --daemonize -p ${test_tmpdir}/mirror-httpd-port
 port=$(cat ${test_tmpdir}/mirror-httpd-port)
 echo "http://127.0.0.1:${port}" > ${test_tmpdir}/mirror-httpd-address